/**
 * @file
 * @brief RTD：XWDS：I2C
 * @author
 * + 隐星魂 (Roy.Sun) <https://xwos.tech>
 * @copyright
 * + (c) 2015 隐星魂 (Roy.Sun) <https://xwos.tech>
 * > Licensed under the Apache License, Version 2.0 (the "License");
 * > you may not use this file except in compliance with the License.
 * > You may obtain a copy of the License at
 * >
 * >         http://www.apache.org/licenses/LICENSE-2.0
 * >
 * > Unless required by applicable law or agreed to in writing, software
 * > distributed under the License is distributed on an "AS IS" BASIS,
 * > WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * > See the License for the specific language governing permissions and
 * > limitations under the License.
 */

#include "../board/std.h"
#include <string.h>
#include <xwos/osal/time.h>
#include <xwos/osal/sync/cond.h>
#include <xwos/osal/lock/spinlock.h>
#include <xwcd/ds/i2c/master.h>
#include "Mcl.h"
#include "CDD_I2c.h"

struct mcalxwds_lpi2cm_driver_data {
        xwu8_t channel;
        I2c_HwUnitType hw;
        const Lpi2c_Ip_MasterConfigType * ipcfg;
        struct {
                struct xwos_splk lock;
                struct xwos_cond completion;
                __xwcc_alignl1cache xwu8_t buffer[64];
        } xfer;
};

static
xwer_t mcalxwds_lpi2cm_drv_probe(struct xwds_device * dev);

static
xwer_t mcalxwds_lpi2cm_drv_remove(struct xwds_device * dev);

static
xwer_t mcalxwds_lpi2cm_drv_start(struct xwds_device * dev);

static
xwer_t mcalxwds_lpi2cm_drv_stop(struct xwds_device * dev);

#if defined(XWCDCFG_ds_PM) && (1 == XWCDCFG_ds_PM)
static
xwer_t mcalxwds_lpi2cm_drv_suspend(struct xwds_device * dev);

static
xwer_t mcalxwds_lpi2cm_drv_resume(struct xwds_device * dev);
#endif

static
xwer_t mcalxwds_lpi2cm_drv_xfer(struct xwds_i2cm * i2cm,
                                struct xwds_i2c_msg * msg,
                                xwtm_t to);

struct xwds_i2cm_driver mcalxwds_lpi2cm_drv = {
        .base = {
                .name = "mcalxwds.lpi2cm",
                .probe = mcalxwds_lpi2cm_drv_probe,
                .remove = mcalxwds_lpi2cm_drv_remove,
                .start = mcalxwds_lpi2cm_drv_start,
                .stop =  mcalxwds_lpi2cm_drv_stop,
#if defined(XWCDCFG_ds_PM) && (1 == XWCDCFG_ds_PM)
                .suspend = mcalxwds_lpi2cm_drv_suspend,
                .resume =  mcalxwds_lpi2cm_drv_resume,
#endif
        },
        .xfer = mcalxwds_lpi2cm_drv_xfer,
        .abort = NULL,
};

static
xwer_t mcalxwds_lpi2cm_drv_probe(struct xwds_device * dev)
{
        struct xwds_i2cm * i2cm;
        struct mcalxwds_lpi2cm_driver_data * drvdata;

        i2cm = xwds_cast(struct xwds_i2cm *, dev);
        drvdata = i2cm->dev.data;
        xwos_splk_init(&drvdata->xfer.lock);
        xwos_cond_init(&drvdata->xfer.completion);
        return XWOK;
}

static
xwer_t mcalxwds_lpi2cm_drv_remove(struct xwds_device * dev)
{
        struct xwds_i2cm * i2cm;
        struct mcalxwds_lpi2cm_driver_data * drvdata;

        i2cm = xwds_cast(struct xwds_i2cm *, dev);
        drvdata = i2cm->dev.data;
        xwos_cond_fini(&drvdata->xfer.completion);
        return XWOK;
}

static
xwer_t mcalxwds_lpi2cm_drv_start(struct xwds_device * dev)
{
        XWOS_UNUSED(dev);
        return XWOK;
}

static
xwer_t mcalxwds_lpi2cm_drv_stop(struct xwds_device * dev)
{
        XWOS_UNUSED(dev);
        return XWOK;
}

#if defined(XWCDCFG_ds_PM) && (1 == XWCDCFG_ds_PM)
static
xwer_t mcalxwds_lpi2cm_drv_suspend(struct xwds_device * dev)
{
        return mcalxwds_lpi2cm_drv_stop(dev);
}

static
xwer_t mcalxwds_lpi2cm_drv_resume(struct xwds_device * dev)
{
        return mcalxwds_lpi2cm_drv_start(dev);
}
#endif

xwer_t mcalxwds_lpi2cm_drv_xfer(struct xwds_i2cm * i2cm,
                                struct xwds_i2c_msg * msg,
                                xwtm_t to)
{
        struct mcalxwds_lpi2cm_driver_data * drvdata;
        const Lpi2c_Ip_MasterConfigType * ipcfg;
        I2c_RequestType mcal_i2c_msg;
        Std_ReturnType mcal_rc;
        Lpi2c_Ip_StatusType ip_rc;
        xwer_t rc;
        xwreg_t cpuirq;
        union xwos_ulock lock;
        xwsq_t lkst;

        drvdata = i2cm->dev.data;
        ipcfg = drvdata->ipcfg;
        lock.osal.splk = &drvdata->xfer.lock;

        mcal_i2c_msg.SlaveAddress = (msg->addr >> (xwu16_t)1);
        if ((xwu16_t)XWDS_I2C_F_10BITADDR & msg->flag) {
                mcal_i2c_msg.BitsSlaveAddressSize = TRUE;
        } else {
                mcal_i2c_msg.BitsSlaveAddressSize = FALSE;
        }
        if (ipcfg->OperatingMode >= LPI2C_HIGHSPEED_MODE) {
                mcal_i2c_msg.HighSpeedMode = TRUE;
        } else {
                mcal_i2c_msg.HighSpeedMode = FALSE;
        }
        mcal_i2c_msg.ExpectNack = FALSE;
        if ((xwu16_t)XWDS_I2C_F_STOP & msg->flag) {
                mcal_i2c_msg.RepeatedStart = FALSE;
        } else {
                mcal_i2c_msg.RepeatedStart = TRUE;
        }
        mcal_i2c_msg.BufferSize = (xwu16_t)msg->size;
        mcal_i2c_msg.DataBuffer = drvdata->xfer.buffer;
        if ((xwu16_t)XWDS_I2C_F_RD & msg->flag) {
                mcal_i2c_msg.DataDirection = I2C_RECEIVE_DATA;
        } else {
                mcal_i2c_msg.DataDirection = I2C_SEND_DATA;
                memcpy(drvdata->xfer.buffer, msg->data, msg->size);
#if defined(CPUCFG_DCACHE) && (1 == CPUCFG_DCACHE)
                Mcl_CacheCleanByAddr(MCL_CACHE_DATA, false,
                                     (xwu32_t)drvdata->xfer.buffer,
                                     (xwu32_t)sizeof(drvdata->xfer.buffer));
#endif
        }

        xwos_splk_lock_cpuirqsv(&drvdata->xfer.lock, &cpuirq);
        mcal_rc = I2c_AsyncTransmit(drvdata->channel, &mcal_i2c_msg);
        if (E_OK != mcal_rc) {
                rc = -EBUSY;
                goto err_xfer;
        }
        rc = xwos_cond_wait_to(&drvdata->xfer.completion, lock, XWOS_LK_SPLK,
                               NULL, to, &lkst);
        if (XWOK == rc) {
                XWOS_BUG_ON((xwsq_t)XWOS_LKST_UNLOCKED == lkst);
                ip_rc = Lpi2c_Ip_MasterGetTransferStatus(drvdata->hw, NULL);
                xwos_splk_unlock_cpuirqrs(&drvdata->xfer.lock, cpuirq);
                switch (ip_rc) {
                case LPI2C_IP_SUCCESS_STATUS:
                        rc = XWOK;
                        if ((xwu16_t)XWDS_I2C_F_RD & msg->flag) {
#if defined(CPUCFG_DCACHE) && (1 == CPUCFG_DCACHE)
                                Mcl_CacheInvalidateByAddr(MCL_CACHE_DATA,
                                                          (xwu32_t)drvdata->xfer.buffer,
                                                          (xwu32_t)sizeof(drvdata->xfer.buffer));
#endif
                                memcpy(msg->data, drvdata->xfer.buffer, msg->size);
                        }
                        break;
                case LPI2C_IP_TX_UNDERRUN_STATUS:
                case LPI2C_IP_RX_OVERRUN_STATUS:
                        rc = -EOVERFLOW;
                        break;
                case LPI2C_IP_ARBITRATION_LOST_STATUS:
                        rc = -EAGAIN;
                        break;
                case LPI2C_IP_ABORTED_STATUS:
                        rc = -ECONNABORTED;
                        break;
                case LPI2C_IP_BUSY_STATUS:
                case LPI2C_IP_BUS_BUSY_STATUS:
                        rc = -EBUSY;
                        break;
                case LPI2C_IP_ERROR_STATUS:
                case LPI2C_IP_RECEIVED_NACK_STATUS:
                case LPI2C_IP_TIMEOUT_STATUS:
                case LPI2C_IP_UNSUPPORTED_STATUS:
                case LPI2C_IP_DMA_ERROR_STATUS:
                default:
                        rc = -EIO;
                        break;
                }
        } else {
                if ((xwsq_t)XWOS_LKST_LOCKED == lkst) {
                        xwos_splk_unlock_cpuirqrs(&drvdata->xfer.lock, cpuirq);
                } else {
                        xwos_cpuirq_restore_lc(cpuirq);
                }
        }
        return rc;

err_xfer:
        xwos_splk_unlock_cpuirqrs(&drvdata->xfer.lock, cpuirq);
        return rc;
}

/******** ******** lpi2c0m ******** ********/
struct mcalxwds_lpi2cm_driver_data mcalxwds_lpi2c0m_drvdata = {
        .channel = 0,
        .hw = I2C_LPI2C_0,
        .ipcfg = &I2c_Lpi2cMasterChannel0_BOARD_InitPeripherals,
};

struct xwds_i2cm mcalxwds_lpi2c0m = {
        /* attributes */
        .dev = {
                .name = "mcalxwds.lpi2c.m",
                .id = 0,
                .resources = NULL,
                .drv = xwds_cast(struct xwds_driver *, &mcalxwds_lpi2cm_drv),
                .data = (void *)&mcalxwds_lpi2c0m_drvdata,
        },
};

/******** ******** Callback ******** ********/
struct xwds_i2cm * const mcalxwds_lpi2cm_devices[] = {
        [0] = &mcalxwds_lpi2c0m,
};

void mcalxwds_lpi2cm_callback(xwu8_t event, xwu8_t data)
{
        struct xwds_i2cm * i2cm;
        struct mcalxwds_lpi2cm_driver_data * drvdata;

        XWOS_UNUSED(event);
        i2cm = mcalxwds_lpi2cm_devices[data];
        drvdata = i2cm->dev.data;
        xwos_cond_broadcast(&drvdata->xfer.completion);
}
